package liuyao.webutils.utils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Base64;

public class NcmUtil {

    public static void main(String[] args) throws Exception {
        File file = new File("/Users/liuyao/Desktop/艾辰 - 错位时空.ncm");
        FileInputStream fis = new FileInputStream(file);
        convertToMp3(fis);
    }

    public static byte[] convertToMp3(InputStream is) throws Exception {

        final byte[] _flag = {0x43, 0x54, 0x45, 0x4e, 0x46, 0x44, 0x41, 0x4d};
        final byte[] _coreBoxKey = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57};
        final byte[] _modifyBoxKey = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28};

        {
            byte[] flag = new byte[_flag.length];
            is.read(flag);
            is.skip(2);// 跳过无用的2字节
            if (!byteEqual(_flag, flag)) {
                throw new RuntimeException("不是标准ncm文件");
            }
        }
        byte[] finalKey;
        {
            byte[] coreKeyChunk = readChunk(is);
            for (int i = 0; i < coreKeyChunk.length; i++) {
                coreKeyChunk[i] ^= 0x64;
            }
            int ckcLen = aesDecrypt(coreKeyChunk, _coreBoxKey);
            finalKey = new byte[ckcLen - 17];
            System.arraycopy(coreKeyChunk, 17, finalKey, 0, finalKey.length);
        }

        byte[] _keyBox;
        {
            _keyBox = new byte[256];
            for (int i = 0; i < _keyBox.length; i++) {
                _keyBox[i] = (byte) i;
            }
            byte swap;
            int c;
            int last_byte = 0;
            int key_offset = 0;
            for (int i = 0; i < _keyBox.length; i++) {
                swap = _keyBox[i];
                c = (swap + last_byte + finalKey[key_offset++]) & 0xff;
                if (key_offset >= finalKey.length) key_offset = 0;
                _keyBox[i] = _keyBox[c];
                _keyBox[c] = swap;
                last_byte = c;
            }
        }

        byte[] dontModifyChunk = readChunk(is);
        int startIndex = 0;
        for (int i = 0; i < dontModifyChunk.length; i++) {
            dontModifyChunk[i] ^= 0x63;
            if (dontModifyChunk[i] == 58 && startIndex == 0) {
                startIndex = i + 1;
            }
        }

        byte[] aa = new byte[dontModifyChunk.length - startIndex];
        System.arraycopy(dontModifyChunk, startIndex, aa, 0, aa.length);
        String a = new String(aa, Charset.forName("utf-8"));
        byte[] dontModifyDecryptChunk = Base64.getDecoder().decode(a);
        int mdcLen = aesDecrypt(dontModifyDecryptChunk, _modifyBoxKey);

                    /*
                    歌曲名 歌手 专辑等信息
            DataContractJsonSerializer d = new DataContractJsonSerializer(typeof(NeteaseCopyrightData));
            // skip `music:`
            using (MemoryStream reader = new MemoryStream(dontModifyDecryptChunk, 6, mdcLen - 6))
            {
                _cdata = d.ReadObject(reader) as NeteaseCopyrightData;
            }
         */


        // skip crc & some use less chunk/
        is.skip(9);

        byte[] imageChunk = readChunk(is);
        byte[] _cover = imageChunk;

        // ------- 元数据提取完成 ----------

        int available = is.available();
        byte[] audio = new byte[available];
        is.read(audio);
//        byte[] audio = is.readAllBytes();

        for (int i = 0; i < audio.length; i++) {
            int j = (i + 1) & 0xff;
            audio[i] ^= _keyBox[(_keyBox[j] + _keyBox[(_keyBox[j] + j) & 0xff]) & 0xff];
        }

        // 写入专辑封面
//        TagLib.ByteVector byteVector = new TagLib.ByteVector(_cover);
//        TagLib.Picture picture = new TagLib.Picture(byteVector);
//        TagLib.Mpeg.AudioFile audioFile = new TagLib.Mpeg.AudioFile(destFilePath);
//        var tags = audioFile.GetTag(TagLib.TagTypes.Id3v2);
//        TagLib.Id3v2.AttachedPictureFrame[] attachedPictureFrames = new TagLib.Id3v2.AttachedPictureFrame[1];
//        TagLib.Id3v2.AttachedPictureFrame attachedPictureFrame = new TagLib.Id3v2.AttachedPictureFrame(picture);
//        attachedPictureFrames[0] = attachedPictureFrame;
//        tags.Pictures = attachedPictureFrames;
//        audioFile.Save();
        return audio;
    }

    private static int aesDecrypt(byte[] data, byte[] key) throws Exception {
        byte[] doFinal = CipherUtil.aesDecrypt_256_ECB_Pkcs7Padding(data, key);
        for (int i = 0; i < doFinal.length; i++) {
            data[i] = doFinal[i];
        }
        return doFinal.length;
    }

    static byte[] readChunk(InputStream is) throws IOException {
        byte[] b = new byte[4];
        is.read(b);
        long len = BinUtil.toUnsignedInt(b[0], b[1], b[2], b[3]);
        b = new byte[(int) len];
        is.read(b);
        return b;
    }

    static boolean byteEqual(byte[] a, byte[] b) {
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i]) return false;
        }
        return true;
    }
}
